package com.android.hitch.network;

//This file contains material supporting section 3.7 of the textbook:
//"Object Oriented Software Engineering" and is issued under the open-source
//license found at www.lloseng.com 

import java.io.*;
import java.net.*;

/**
 * The <code> AbstractClient </code> contains all the
 * methods necessary to set up the client side of a client-server
 * architecture.  When a client is thus connected to the
 * server, the two programs can then exchange <code> Object </code>
 * instances.<p>
 *
 * Method <code> handleMessageFromServer </code> must be defined by
 * a concrete subclass. Several other hook methods may also be
 * overriden.<p>
 *
 * Several public service methods are provided to
 * application that use this framework.<p>
 *
 * Project Name: OCSF (Object Client-Server Framework)<p>
 *
 * @author Dr. Robert Lagani&egrave;re
 * @author Dr. Timothy C. Lethbridge
 * @author Fran&ccedil;ois  B&eacutel;langer
 * @author Paul Holden
 * @version February 2001 (2.12)
 */
public abstract class AbstractClient implements Runnable
{

	//INSTANCE VARIABLES ***********************************************

	/**
	 * Sockets are used in the operating system as channels
	 * of communication between two processes.
	 * @see java.net.Socket
	 */
	private Socket clientSocket;

	/**
	 * The stream to handle data going to the server.
	 */
	private DataOutputStream output;

	/**
	 * The stream to handle data from the server.
	 */
	private DataInputStream input;

	/**
	 * The thread created to read data from the server.
	 */
	private Thread clientReader;

	/**
	 * Indicates if the thread is ready to stop.
	 * Needed so that the loop in the run method knows when to stop
	 * waiting for incoming messages.
	 */
	private boolean readyToStop= false;

	/**
	 * The server's host name.
	 */
	private String host;

	/**
	 * The port number.
	 */
	private int port;

	//CONSTRUCTORS *****************************************************

	/**
	 * Constructs the client.
	 *
	 * @param  host  the server's host name.
	 * @param  port  the port number.
	 */
	public AbstractClient(String host, int port)
	{
		// Initialize variables
		this.host = host;
		this.port = port;
	}

	//INSTANCE METHODS *************************************************

	/**
	 * Opens the connection with the server.
	 * If the connection is already opened, this call has no effect.
	 *
	 * @exception IOException if an I/O error occurs when opening.
	 */
	final public void openConnection() throws IOException
	{
		// Do not do anything if the connection is already open
		if(isConnected())
			return;

		//Create the sockets and the data streams
		try
		{
			clientSocket= new Socket(host, port);
			output = new DataOutputStream(clientSocket.getOutputStream());
			input = new DataInputStream(clientSocket.getInputStream());
		}
		catch (IOException ex)
		// All three of the above must be closed when there is a failure
		// to create any of them
		{
			try
			{
				closeAll();
			}
			catch (Exception exc) { }

			throw ex; // Rethrow the exception.
		}

		clientReader = new Thread(this);  //Create the data reader thread
		readyToStop = false;
		clientReader.start();  //Start the thread
	}

	/**
	 * Sends an object to the server. This is the only way that
	 * methods should communicate with the server.
	 *
	 * @param msg   The message to be sent.
	 * @exception IOException if an I/O error occurs when sending
	 */
	final public void sendToServer(byte msg[]) throws IOException
	{
		if (clientSocket == null || output == null)
			throw new SocketException("socket does not exist");

		output.write(msg);
		output.flush();
	}

	/**
	 * Closes the connection to the server.
	 *
	 * @exception IOException if an I/O error occurs when closing.
	 */
	final public void closeConnection() throws IOException
	{
		// Prevent the thread from looping any more
		readyToStop= true;

		try
		{
			closeAll();
		}
		finally
		{
			// Call the hook method
			connectionClosed();
		}
	}

	//ACCESSING METHODS ------------------------------------------------

	/**
	 * @return true if the client is connnected.
	 */
	final public boolean isConnected()
	{
		return clientReader!=null && clientReader.isAlive();
	}

	/**
	 * @return the port number.
	 */
	final public int getPort()
	{
		return port;
	}

	/**
	 * Sets the server port number for the next connection.
	 * The change in port only takes effect at the time of the
	 * next call to openConnection().
	 *
	 * @param port the port number.
	 */
	final public void setPort(int port)
	{
		this.port = port;
	}

	/**
	 * @return the host name.
	 */
	final public String getHost()
	{
		return host;
	}

	/**
	 * Sets the server host for the next connection.
	 * The change in host only takes effect at the time of the
	 * next call to openConnection().
	 *
	 * @param host the host name.
	 */
	final public void setHost(String host)
	{
		this.host = host;
	}

	/**
	 * returns the client's description.
	 *
	 * @return the client's Inet address.
	 */
	final public InetAddress getInetAddress()
	{
		return clientSocket.getInetAddress();
	}

	//RUN METHOD -------------------------------------------------------

	/**
	 * Waits for messages from the server. When each arrives,
	 * a call is made to <code>handleMessageFromServer()</code>.
	 * Not to be explicitly called.
	 */
	final public void run()
	{
		connectionEstablished();

		// Loop waiting for data

		try
		{
			while(!readyToStop)
			{
				// Get data from Server and send it to the handler
				// The thread waits indefinitely at the following
				// statement until something is received from the server
				if (input.available()>0) {
					byte[] buf = new byte[input.available()];
					input.read(buf);
				
					// Concrete subclasses do what they want with the
					// msg by implementing the following method
					handleMessageFromServer(buf);
				}
				Thread.sleep(10);
			}
		}
		catch (Exception exception)
		{
			if(!readyToStop)
			{
				try
				{
					closeAll();
				}
				catch (Exception ex) { }

				connectionException(exception);
			}
		}
		finally
		{
			clientReader = null;
		}
	}

	//METHODS DESIGNED TO BE OVERRIDDEN BY CONCRETE SUBCLASSES ---------

	/**
	 * Hook method called after the connection has been closed.
	 * The default implementation does nothing. The method
	 * may be overriden by subclasses to perform special processing
	 * such as cleaning up and terminating, or attempting to
	 * reconnect.
	 */
	protected void connectionClosed() {}

	/**
	 * Hook method called each time an exception is thrown by the
	 * client's thread that is waiting for messages from the server.
	 * The method may be overridden by subclasses.
	 *
	 * @param exception the exception raised.
	 */
	protected void connectionException(Exception exception) {}

	/**
	 * Hook method called after a connection has been established.
	 * The default implementation does nothing.
	 * It may be overridden by subclasses to do anything they wish.
	 */
	protected void connectionEstablished() {}

	/**
	 * Handles a message sent from the server to this client.
	 * This MUST be implemented by subclasses, who should respond to
	 * messages.
	 *
	 * @param msg   the message sent.
	 */
	protected abstract void handleMessageFromServer(byte msg[]);


	//METHODS TO BE USED FROM WITHIN THE FRAMEWORK ONLY ----------------

	/**
	 * Closes all aspects of the connection to the server.
	 *
	 * @exception IOException if an I/O error occurs when closing.
	 */
	private void closeAll() throws IOException
	{
		try
		{
			//Close the socket
			if (clientSocket != null)
				clientSocket.close();

			//Close the output stream
			if (output != null)
				output.close();

			//Close the input stream
			if (input != null)
				input.close();
		}
		finally
		{
			// Set the streams and the sockets to NULL no matter what
			// Doing so allows, but does not require, any finalizers
			// of these objects to reclaim system resources if and
			// when they are garbage collected.
			output = null;
			input = null;
			clientSocket = null;
		}
	}
}
//end of AbstractClient class
